/*
dictziploader.cpp
(c) 2010 Yury Hamenkov
This file is part of qWordCards.

qWordCards is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

qWordCards is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with qWordCards. If not, see <http://www.gnu.org/licenses/>.
*/


#include "dictziploader.h"
#include <QFile>
#ifdef DEBUG
	#include <QDebug>
#endif

using namespace std;
using namespace qWordCards;

DICTZIPLoader::DICTZIPLoader(QString _fileName) :
		ID1(0),
		ID2(0), 
		CM(0), 
		FLG(0), 
		MTIME(0), 
		XFL(0), 
		OS(0), 
		type(UNKNOWN), 
		fileName(_fileName), 
		loaded(false),
		initialized(false),
		headerLength(0),
		FLG_FNAME(0),
		FLG_FCOMMENT(0),
		FLG_FHCRC(0),
		FLG_FEXTRA_XLEN(0),
		FLG_FEXTRA_SI1(0),
		FLG_FEXTRA_SI2(0),
		FLG_FEXTRA_LEN(0),
		FLG_FEXTRA_DZIP_VER(0),
		FLG_FEXTRA_DZIP_CHLEN(0),
		FLG_FEXTRA_DZIP_CHCNT(0),
		FLG_FEXTRA_DZIP_CHUNKS(0),
		FLG_FEXTRA_DZIP_OFFSETS(0)
{
	Q_ASSERT(_fileName != "");
	//qDebug() << _fileName;
	Q_ASSERT(QFile(_fileName).exists());
}

DICTZIPLoader::~DICTZIPLoader() {
	if (FLG_FNAME != 0) {
		delete[] FLG_FNAME;
	}
	
	if (FLG_FCOMMENT != 0) {
		delete[] FLG_FCOMMENT;
	}
}

bool DICTZIPLoader::Load() {
	if (loaded) {return true;}
	
	ifstream file;
	file.open(fileName.toStdString().c_str(), ios::binary | ios::in);
	if (!file) {
		qDebug() << "Cannot open file '" << fileName << "'\n";
		return false;
	}
	headerLength = 0;
	
	char c;
	file.read(&c, sizeof(c));
	ID1 = (unsigned char)c;
	file.read(&c, sizeof(c));
	ID2 = (unsigned char)c;
	
	headerLength = headerLength + sizeof(c) + sizeof(c);
	
	if (ID1 != 0x1f || ID2 != 0x8b) {
		file.close();
		qDebug() << QString("File '%1' is not a GZIP file.").arg(fileName);
		type = TEXT;
		return false;
	}
	
	type = GZIP;
	
	file.get(CM);
	if (CM != 0x08) {
		file.close();
		qDebug() << QString("File '%1' has wrong compression type: '%2'").arg(fileName).arg((int)CM);
		return false;
	}
	headerLength += sizeof(CM);
	
	file.get(FLG);
	headerLength += sizeof(FLG);
	file.read((char*)&MTIME, sizeof(MTIME));
	headerLength += sizeof(MTIME);
	file.get(XFL);
	headerLength += sizeof(XFL);
	file.get(OS);
	headerLength += sizeof(OS);

	
	if ((FLG & 0x01) == 0x01) {
		qDebug() << "File is a text, FTEXT bit is set. Exiting\n";
		file.close();
		return false;
	}
	
	if ((FLG & 0x04) == 0x04) {
		file.read((char*)&FLG_FEXTRA_XLEN, sizeof(FLG_FEXTRA_XLEN));
		headerLength += sizeof(FLG_FEXTRA_XLEN);
		
		file.read(&FLG_FEXTRA_SI1, sizeof(FLG_FEXTRA_SI1));
		headerLength += sizeof(FLG_FEXTRA_SI1);
		
		file.read(&FLG_FEXTRA_SI2, sizeof(FLG_FEXTRA_SI2));
		headerLength += sizeof(FLG_FEXTRA_SI2);
		
		file.read((char*)&FLG_FEXTRA_LEN, sizeof(FLG_FEXTRA_LEN));
		headerLength += sizeof(FLG_FEXTRA_LEN);
		
		
		if (FLG_FEXTRA_SI1 != 'R' || FLG_FEXTRA_SI2 != 'A') {
			qDebug() << "File has no random access\n";
			return false;
		}
		type = DZIP;
		
		file.read((char*)&FLG_FEXTRA_DZIP_VER, sizeof(FLG_FEXTRA_DZIP_VER));
		headerLength += sizeof(FLG_FEXTRA_DZIP_VER);
		
		file.read((char*)&FLG_FEXTRA_DZIP_CHLEN, sizeof(FLG_FEXTRA_DZIP_CHLEN));
		headerLength += sizeof(FLG_FEXTRA_DZIP_CHLEN);
		
		file.read((char*)&FLG_FEXTRA_DZIP_CHCNT, sizeof(FLG_FEXTRA_DZIP_CHCNT));
		headerLength += sizeof(FLG_FEXTRA_DZIP_CHCNT);
		
		FLG_FEXTRA_DZIP_CHUNKS = new short int[FLG_FEXTRA_DZIP_CHCNT];
		if (FLG_FEXTRA_DZIP_CHUNKS == 0) {
			qDebug() << "Cannot allocate memory for chunks data\n";
			exit(1);
		}
		
		for (int i = 0; i < FLG_FEXTRA_DZIP_CHCNT; i++) {
			file.read((char*)&FLG_FEXTRA_DZIP_CHUNKS[i], sizeof(FLG_FEXTRA_DZIP_CHUNKS[0]));
			headerLength += sizeof(FLG_FEXTRA_DZIP_CHUNKS[0]);
		}
		

	}
	// Read FLG.FNAME
	if ((FLG & 0x08) == 0x08) {
		FLG_FNAME = new char[1024];
		file.get(FLG_FNAME, 1024, '\0');
		headerLength += strlen(FLG_FNAME) + 1;
	}
	
	if ((FLG & 0x10) == 0x10) {
		FLG_FCOMMENT = new char[1024];
		file.get(FLG_FCOMMENT, 1024, '\0');
		headerLength += strlen(FLG_FCOMMENT) + 1;
	}
	
	if ((FLG & 0x02) == 0x02) {
		file.read((char*)&FLG_FHCRC, sizeof(FLG_FHCRC));
		headerLength += sizeof(FLG_FHCRC);
	}
	
	// Calculate compressed data chunks offsets
	FLG_FEXTRA_DZIP_OFFSETS = new unsigned long int[FLG_FEXTRA_DZIP_CHCNT];
	unsigned long offset = headerLength;
	for (int i = 0; i < FLG_FEXTRA_DZIP_CHCNT; i++) {
		FLG_FEXTRA_DZIP_OFFSETS[i] = offset;
		offset += FLG_FEXTRA_DZIP_CHUNKS[i];
	}
	
	loaded = true;
	file.close();
	return true;
};

void DICTZIPLoader::PrintDetailData() {
	std::string printOffset = "";
	
	qDebug() << "\nSummary\n";
	qDebug() << "File : '" << fileName << "'\n";
	qDebug() << "ID1: '" << ID1 << "', ID2: '" << ID2 << "'\n";
	qDebug() << "Compression method: " << dec << (int)CM << '\n';
	qDebug() << "Flag: " << hex << (int)FLG << "\n";
	qDebug() << "\nMTIME: " << dec << MTIME << "\n";
	qDebug() << "XFL: " << dec << XFL << "\n";
	qDebug() << "OS: (" << dec << OS << ") ";
	switch((unsigned char)OS) {
		case 0: qDebug() << "FAT filesystem (MS-DOS, OS/2, NT/Win32)\n"; break;
		case 1: qDebug() << "Amiga\n"; break;
		case 2: qDebug() << "VMS (or OpenVMS)\n"; break;
		case 3: qDebug() << "Unix\n"; break;
		case 4: qDebug() << "VM/CMS\n"; break;
		case 5: qDebug() << "Atari TOS\n"; break;
		case 6: qDebug() << "HPFS filesystem (OS/2, NT)\n"; break;
		case 7: qDebug() << "Macintosh\n"; break;
		case 8: qDebug() << "Z-System\n"; break;
		case 9: qDebug() << "CP/M\n"; break;
		case 10: qDebug() << "TOPS-20\n"; break;
		case 11: qDebug() << "NTFS filesystem (NT)\n"; break;
		case 12: qDebug() << "QDOS\n"; break;
		case 13: qDebug() << "Acorn RISCOS\n"; break;
		case 255: qDebug() << "unknown\n"; break;
		default:  break;
	};
	
	qDebug() << "Header Length: " << dec << headerLength << "\n";
	qDebug() << "Chunks data:\n\n";
	
	if (FLG_FNAME != 0) {
		qDebug() << "FLG_FNAME: '" << FLG_FNAME << "'\n";
	}
	
	if (FLG_FCOMMENT != 0) {
		qDebug() << "FLG_FCOMMENT: '" << FLG_FCOMMENT << "'\n";
	}
	
	if (FLG_FHCRC != 0) {
		qDebug() << "FLG_FHCRC: " << dec << FLG_FHCRC << "\n";
	}
	
	if (FLG_FEXTRA_XLEN != 0) {
		qDebug() << "FLG_FEXTRA_XLEN: " << dec << FLG_FEXTRA_XLEN << "\n";
		qDebug() << "FLG_FEXTRA_SI1: " << FLG_FEXTRA_SI1 << "\n";
		qDebug() << "FLG_FEXTRA_SI2: " << FLG_FEXTRA_SI2 << "\n";
		qDebug() << "FLG_FEXTRA_LEN: " << dec << FLG_FEXTRA_LEN << "\n";
		
		if (type == DZIP) {
			qDebug() << "FLG_FEXTRA_DZIP_VER: " << dec << FLG_FEXTRA_DZIP_VER << "\n";
			qDebug() << "FLG_FEXTRA_DZIP_CHLEN: " << dec << FLG_FEXTRA_DZIP_CHLEN << "\n";
			qDebug() << "FLG_FEXTRA_DZIP_CHCNT: " << dec << FLG_FEXTRA_DZIP_CHCNT << "\n";
			
			qDebug() << "FLG_FEXTRA_DZIP_CHUNKS data: \n";
			for (int i = 0; i < FLG_FEXTRA_DZIP_CHCNT; i++) {
				qDebug() << "\t" << dec << FLG_FEXTRA_DZIP_CHUNKS[i] << "\n";
			}
			
			qDebug() << "\nFLG_FEXTRA_DZIP_OFFSETS data: \n";
			for (int i = 0; i < FLG_FEXTRA_DZIP_CHCNT; i++) {
				qDebug() << "\t" << dec << FLG_FEXTRA_DZIP_OFFSETS[i] << "\n";
			}
		}
	}
}

//QString DICTZIPLoader::ReadData (const WordIndex& w) {
//	return ReadData(w.GetOffset(), w.GetSize());
//}

QString DICTZIPLoader::ReadData (unsigned long int start, unsigned long int size) {
	if (!loaded) {
		qDebug() << "File is not loaded\n";
		return "";
	}
	if (type != DZIP) {
		qDebug() << "File is not a dictzip file\n";
		return "";
	}
	
	unsigned long int end = start + size;
	char* buffer = new char[size + 1];
	char* pt = 0;
	
	int firstChunk, lastChunk;
	int firstOffset, lastOffset;
	
	int found, target;
	
	int count;
	char *inBuffer = 0;
	char outBuffer[OUT_BUFFER_SIZE];
	
	if (!initialized) {
		initialized = true;
		zStream.zalloc    = NULL;
		zStream.zfree     = NULL;
		zStream.opaque    = NULL;
		zStream.next_in   = 0;
		zStream.avail_in  = 0;
		zStream.next_out  = NULL;
		zStream.avail_out = 0;
		if (inflateInit2( &zStream, -15 ) != Z_OK) {
			printf("Cannot initialize inflation engine: %s\n", zStream.msg );
			return 0;
		}
	}
	
	firstChunk  = start / FLG_FEXTRA_DZIP_CHLEN;
	firstOffset = start - firstChunk * FLG_FEXTRA_DZIP_CHLEN;
	lastChunk   = end / FLG_FEXTRA_DZIP_CHLEN;
	lastOffset  = end - lastChunk * FLG_FEXTRA_DZIP_CHLEN;
	printf("Unzip. start = %lu, end = %lu, firstChunk = %d, firstOffset = %d, lastChunk = %d, lastOffset = %d\n",
	  start, end, firstChunk, firstOffset, lastChunk, lastOffset );
	
	ifstream file;
	file.open(fileName.toStdString().c_str(), ios::binary | ios::in);
	if (!file) {
		qDebug() << "Cannot open file '" << fileName << "'\n";
		return 0;
	}
	
	//------------------------------------------------
	inBuffer = new char[IN_BUFFER_SIZE];
	
	for (int i = firstChunk; i <= lastChunk; i++) {
		pt = buffer;
		found  = 0;
		target = 0;
		
		if (FLG_FEXTRA_DZIP_CHUNKS[i] >= OUT_BUFFER_SIZE ) {
			printf("FLG_FEXTRA_DZIP_CHUNKS[%d] = %d >= %ld (OUT_BUFFER_SIZE)\n", i, FLG_FEXTRA_DZIP_CHUNKS[i], OUT_BUFFER_SIZE );
		}
		
		file.seekg(FLG_FEXTRA_DZIP_OFFSETS[i], ios::beg);
		file.read(outBuffer, FLG_FEXTRA_DZIP_CHUNKS[i]);
		qDebug() << "Reading at offset: " << FLG_FEXTRA_DZIP_OFFSETS[i] << "\n";
		qDebug() << "Reading data with size: " << FLG_FEXTRA_DZIP_CHUNKS[i] << "\n";
		//qDebug() << outBuffer << "\n";
		
		zStream.next_in   = reinterpret_cast<Bytef*>(outBuffer);
		zStream.avail_in  = FLG_FEXTRA_DZIP_CHUNKS[i];
		zStream.next_out  = reinterpret_cast<Bytef*>(inBuffer);
		zStream.avail_out = IN_BUFFER_SIZE;
		
		int result = inflate( &zStream,  Z_SYNC_FLUSH );
		qDebug() << "Inflate result: " << result << "\n";
		
		if ( result != Z_OK) {
			printf("inflate: %s\n", zStream.msg );
		}
		
		if (zStream.avail_in) {
			printf("inflate did not flush (%d pending, %d avail)\n", zStream.avail_in, zStream.avail_out );
		}
		
		count = IN_BUFFER_SIZE - zStream.avail_out;

		if (i == firstChunk) {
			if (i == lastChunk) {
				memcpy( pt, inBuffer + firstOffset, lastOffset - firstOffset);
				pt += lastOffset - firstOffset;
			} else {
				if (count != FLG_FEXTRA_DZIP_CHLEN ) {
					printf("Length = %d instead of %d\n", count, FLG_FEXTRA_DZIP_CHLEN );
				}
				memcpy( pt, inBuffer + firstOffset,	FLG_FEXTRA_DZIP_CHLEN - firstOffset );
				pt += FLG_FEXTRA_DZIP_CHLEN - firstOffset;
			}
		} else if (i == lastChunk) {
			memcpy( pt, inBuffer, lastOffset );
			pt += lastOffset;
		} else {
			Q_ASSERT(count == FLG_FEXTRA_DZIP_CHLEN);
			memcpy( pt, inBuffer, FLG_FEXTRA_DZIP_CHLEN );
			pt += FLG_FEXTRA_DZIP_CHLEN;
		}
	}
	*pt = '\0';
	QString str(buffer);
	delete []buffer;
	delete []inBuffer;
	return str;
}
